#pragma once

#include <queue>
#include <mutex>
#include <atomic>
#include <memory>
#include <thread>
#include <condition_variable>
#include "MysqlConn.hpp"

namespace mysql {

    class MysqlConnPool {

    public:

        /**
          * @brief  初始化一个数据库连接池
          * @note   连接失败会抛出SQLException
          * @param  最小连接数，最大连接数，ip地址，端口，用户名，密码，数据库名，编码格式(默认utf8)
          */
        MysqlConnPool(int min_conn_count, int max_conn_count,
                      const std::string &ip, int port,
                      const std::string &user, const std::string &password,
                      const std::string &dbname, const std::string &character_set = "utf8");

        ~MysqlConnPool();

        /**
          * @brief  阻塞的向连接池索要一个连接的智能指针
          * @note   若连接池已销毁再申请连接，将会抛出std::runtime_error异常
          * @param  当连接池无可用连接时的执行策略
          *         is_create默认为false
          *         is_create为true时表示无可用连接时自动创建一个
          *         is_create为false时表示无可用连接时一直阻塞等待有可用连接到来
          * @retval 一个指向MysqlConn实例的智能指针
          */
        std::shared_ptr<MysqlConn> getConnection(bool is_create = false);

        /**
          * @brief  释放连接池内的一个连接
          * @retval 成功返回true，失败返回false
          */
        bool release();

        /**
          * @brief  获取存活连接个数
          * @retval 存活连接个数
          */
        int aliveCount() const { return alive_conn_count_; }

        MysqlConnPool() = delete;

        MysqlConnPool(const MysqlConnPool &) = delete;

        MysqlConnPool(MysqlConnPool &&) = delete;

        MysqlConnPool &operator=(const MysqlConnPool &) = delete;

    private:

        /**
          * @brief  为连接池添加连接
          * @note   如果连接失败将会抛出SQLException
          */
        void addConnection();

    private:

        // 最小连接数
        int min_conn_count_;

        // 最大连接数
        int max_conn_count_;

        // ip地址
        std::string ip_;

        // 端口
        int port_;

        // 密码
        std::string password_;

        // 数据库名
        std::string dbname_;

        // 用户名
        std::string user_;

        // 编码格式
        std::string character_set_;

        // 连接队列
        std::queue<MysqlConn *> conn_queue_;

        // 存活连接个数
        std::atomic<int> alive_conn_count_{0};

        // 连接池是否关闭
        std::atomic<bool> stop_{false};

        // 连接队列互斥锁
        std::mutex queue_locker_;

        // 条件变量
        std::condition_variable variable_;

    };

    MysqlConnPool::MysqlConnPool(int min_conn_count, int max_conn_count,
                                 const std::string &ip, int port,
                                 const std::string &user, const std::string &password,
                                 const std::string &dbname, const std::string &character_set) :
            min_conn_count_(min_conn_count),
            max_conn_count_(max_conn_count),
            ip_(ip), port_(port),
            user_(user), password_(password),
            dbname_(dbname), character_set_(character_set) {
        for (int i = 0; i < min_conn_count_; ++i) {
            addConnection();
        }
    }

    MysqlConnPool::~MysqlConnPool() {
        stop_ = true;
        while (true) {
            queue_locker_.lock();
            if (conn_queue_.size() == alive_conn_count_)
                break;
            queue_locker_.unlock();
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (!conn_queue_.empty()) {
            MysqlConn *conn = conn_queue_.front();
            delete conn;
            conn_queue_.pop();
        }
    }

    std::shared_ptr<MysqlConn> MysqlConnPool::getConnection(bool is_create) {
        if (stop_) throw std::runtime_error("Get connection after pool destory");
        std::unique_lock<std::mutex> lck(queue_locker_);
        if (is_create && conn_queue_.empty() && alive_conn_count_ < max_conn_count_) {
            addConnection();
        }
        variable_.wait(lck, [this]() { return !conn_queue_.empty(); });
        std::shared_ptr<MysqlConn> ret(conn_queue_.front(), [this](MysqlConn *conn) {
            queue_locker_.lock();
            conn_queue_.push(conn);
            queue_locker_.unlock();
            variable_.notify_one();
        });
        conn_queue_.pop();
        return ret;
    }

    void MysqlConnPool::addConnection() {
        auto *conn = new MysqlConn;
        conn->connect(user_, password_, dbname_, ip_, port_);
        conn->setCharacterSet(character_set_);
        conn_queue_.push(conn);
        ++alive_conn_count_;
    }

    bool MysqlConnPool::release() {
        queue_locker_.lock();
        if (alive_conn_count_ > min_conn_count_ && !conn_queue_.empty()) {
            MysqlConn *conn = conn_queue_.front();
            delete conn;
            conn_queue_.pop();
            queue_locker_.unlock();
            return true;
        }
        queue_locker_.unlock();
        return false;
    }

}
